import { Locale } from '@/messages/locale'
import _ from 'lodash'

export interface ResourceBundle {
  getString(code: string): string
}

export interface BundleResolver<T> {
  support(messages: any): boolean
  getBundle(messages: T): ResourceBundle
}

class JsonResourceBundle implements ResourceBundle {
  messages: Map<string, string>

  constructor (messages: object) {
    this.messages = new Map()
    this.init(messages)
  }

  getString (code: string): string {
    return this.messages.get(code)
  }

  private init (messages: object) {
    const stack: string[] = []
    this.pushToMap(stack, messages)
  }

  private pushToMap (stack: string[], obj: any) {
    if (_.isString(obj)) {
      const key = stack.join('.')
      this.messages.set(key, obj)
    } else if (_.isObject(obj)) {
      Object.keys(obj).forEach(key => {
        stack.push(key)
        this.pushToMap(stack, obj[key])
        stack.pop()
      })
    }
  }
}

class JsonBundleResolver implements BundleResolver<object> {
  support (messages: any): boolean {
    return _.isObject(messages)
  }
  getBundle (messages: object): ResourceBundle {
    return new JsonResourceBundle(messages)
  }
}

class ResourceBundles {
  bundles: Map<string, ResourceBundle>
  resolvers: Array<BundleResolver<any>>
  defaultResolver: BundleResolver<any>

  constructor (defaultResolver?: BundleResolver<any>) {
    this.bundles = new Map<string, ResourceBundle>()
    this.resolvers = []
    if (defaultResolver) {
      this.defaultResolver = defaultResolver
    } else {
      this.defaultResolver = new JsonBundleResolver()
    }
  }

  clean (): void {
    this.bundles = new Map<string, ResourceBundle>()
  }

  getBundle (locale: Locale): ResourceBundle {
    const bundle = this.bundles.get(locale.getName())
    if (!bundle) {
      throw new Error('No bundle of ' + locale.getName() + ' found.')
    }
    return bundle
  }

  addBundle (locale: Locale, messages: any): ResourceBundles {
    for (const resolver of this.resolvers) {
      if (resolver.support(messages)) {
        this.bundles.set(locale.getName(), resolver.getBundle(messages))
        return this
      }
    }
    if (this.defaultResolver.support(messages)) {
      this.bundles.set(
        locale.getName(),
        this.defaultResolver.getBundle(messages)
      )
      return this
    }
    throw new Error(
      'No message resolve found for message of type:' + typeof messages
    )
  }

  addResolver (resolver: BundleResolver<any>): ResourceBundles {
    this.resolvers = _.concat([resolver], this.resolvers)
    return this
  }
}

export const bundles = new ResourceBundles()
